package ierr

import (
	"fmt"
	"io"
	"runtime"
	"strings"
)

type FuncName string

func (f FuncName) Name() string {
	name := string(f)
	i := strings.LastIndex(name, "/")
	name = name[i+1:]
	i = strings.Index(name, ".")
	return name[i+1:]
}

type Stack struct {
	File     string
	Line     int
	FuncName FuncName
}

type Stacks []Stack

func (ss Stacks) formatString(s fmt.State, split string, full bool) {

	for i, stack := range ss {
		if i > 0 {
			io.WriteString(s, split)
		}

		var m string
		if full {
			m = fmt.Sprintf(" File:%s line:%d FuncName:%s", stack.File, stack.Line, stack.FuncName.Name())
		} else {
			m = fmt.Sprintf(" %s:%d (%s)", stack.File, stack.Line, stack.FuncName.Name())
		}
		io.WriteString(s, m)
	}
}

func (ss Stacks) Format(s fmt.State, verb rune) {
	switch verb {
	case 'v':
		switch {
		case s.Flag('+'):
			ss.formatString(s, " \n ", false)
		case s.Flag('#'):
			ss.formatString(s, " \n ", true)
		default:
			ss.formatString(s, " => ", false)
		}
	case 's':
		ss.formatString(s, " => ", false)
	}
}

type errStacks struct {
	skip   int
	depth  int
	stacks Stacks
}

func newStacks(Option ...OptionFunc) *errStacks {

	s := &errStacks{
		skip:   2,
		depth:  10,
		stacks: make([]Stack, 0),
	}

	if len(Option) > 0 {
		for _, fn := range Option {
			fn(s)
		}
	}

	return s
}

func (ss *errStacks) resolve() {
	var i = 0
	for i < ss.depth {
		if pc, file, line, ok := runtime.Caller(i + ss.skip); ok {
			s := Stack{
				File:     file,
				Line:     line,
				FuncName: FuncName(runtime.FuncForPC(pc).Name()),
			}
			ss.stacks = append(ss.stacks, s)
			i++
		} else {
			break
		}
	}
}

type OptionFunc func(ss *errStacks)

func WithSkip(skip int) OptionFunc {
	return func(ss *errStacks) {
		ss.skip = skip
	}
}

func WithDepth(depth int) OptionFunc {
	return func(es *errStacks) {
		es.depth = depth
	}
}
